package edu.udo.sopra10.chaturaji.entities;

import static edu.udo.sopra10.chaturaji.util.ExceptionUtil.assertNotNull;
import static edu.udo.sopra10.chaturaji.util.ExceptionUtil.assertNotNullOrContainsNull;
import static java.util.Collections.unmodifiableList;
import static org.apache.commons.lang.builder.ToStringStyle.SHORT_PREFIX_STYLE;

import java.util.Collection;
import java.util.LinkedList;
import java.util.List;

import org.apache.commons.lang.builder.ToStringBuilder;

import edu.udo.sopra10.chaturaji.entities.coordinates.Position;
import edu.udo.sopra10.chaturaji.entities.terminationconditions.TerminationCondition;

/**
 * A single game of chaturaji. This contains the game state and modus, the
 * movement history etc. A game typically corresponds to the information stored
 * in a single save file.
 * 
 * @author Simon Dierl
 */
public final class Game extends AbstractGame {
	/**
	 * All {@link Turn}s made during the game, in chronological order.
	 */
	private final List<Turn> history = new LinkedList<Turn>();

	/**
	 * The condition under which the game terminates.
	 */
	private TerminationCondition terminationCondition;

	/**
	 * Create a new game with the specified termination condition.
	 * 
	 * @param terminationCondition
	 *            the condition for game termination, must not be {@code null}.
	 */
	public Game(final TerminationCondition terminationCondition) {
		setTerminationCondition(terminationCondition);
		for (byte x = 0; x < 8; x++) {
			for (byte y = 0; y < 8; y++) {
				board[x][y] = new Square(this, new Position(x, y));
			}
		}
	}

	/**
	 * Append a {@link Turn} to the list of turns made during the game. The turn
	 * must be the newest turn made.
	 * 
	 * @param turn
	 *            the new turn, must not be {@code null}.
	 * @return if the turn was new to the list. Always returns {@code true}.
	 */
	public boolean addHistoryTurn(final Turn turn) {
		return history.add(assertNotNull(turn, "turn"));
	}

	/**
	 * Append several {@link Turn}s to the list of turns made during the game.
	 * The turns must be the newest turns made and their internal ordering must
	 * also be chronological.
	 * 
	 * @param turns
	 *            the new turns, must not be {@code null} or contain {code
	 *            null}. This list is copied to preserve encapsulation.
	 * @return if one if the turns was new to the list. Always returns
	 *         {@code !turns.isEmpty()}.
	 */
	public boolean addHistoryTurns(final List<Turn> turns) {
		return history.addAll(assertNotNullOrContainsNull(turns, "turns"));
	}

	/**
	 * Append a {@link Player} to the list of player participating in the game.
	 * The player must make her move after all players currently in the list.
	 * 
	 * @param player
	 *            the new player, must not be {@code null}.
	 * @return if the player was new to the list. Always returns {@code true}.
	 */
	public boolean addPlayer(final Player player) {
		return players.add(assertNotNull(player, "player"));
	}

	/**
	 * Append several {@link Player}s to the list of players participating in
	 * the game. The players must make their moves after all players currently
	 * in the list and their internal ordering must be in turn order.
	 * 
	 * @param players
	 *            the new players, must not be {@code null}.
	 * @return if one of the players was new to the list. Always returns
	 *         {@code !players.isEmpty()}.
	 */
	public boolean addPlayers(final List<Player> players) {
		return this.players.addAll(assertNotNullOrContainsNull(players,
				"players"));
	}

	/**
	 * Remove all {@link Turn}s from the list of turns made during the game.
	 */
	public void clearHistory() {
		history.clear();
	}

	/**
	 * Remove all {@link Player}s from the list of players participating in the
	 * game.
	 */
	public void clearPlayers() {
		players.clear();
	}

	/**
	 * Get all {@link Turn}s made during this game.
	 * 
	 * @return all turns made, in chronological order, never {@code null}, never
	 *         contains {@code null}, always unmodifiable.
	 */
	public List<Turn> getHistory() {
		return unmodifiableList(history);
	}

	/**
	 * Get the termination condition for the game.
	 * 
	 * @return the condition for game termination, never {@code null}.
	 */
	public TerminationCondition getTerminationCondition() {
		return terminationCondition;
	}

	/**
	 * Remove a {@link Turn} from the list of turns made during the game.
	 * 
	 * @param turn
	 *            the turn to remove, must not be {@code null}.
	 * @return if the turn was contained in the list.
	 */
	public boolean removeHistoryTurn(final Turn turn) {
		return history.remove(assertNotNull(turn, "turn"));
	}

	/**
	 * Remove several {@link Turn}s from the list of turns during the game.
	 * 
	 * @param turns
	 *            the turns to remove, must not be {@code null}. May contain
	 *            {@code null}, although this has no effect and is discouraged.
	 * @return if any of the turns were contained in the list.
	 */
	public boolean removeHistoryTurns(final Collection<Turn> turns) {
		return history.removeAll(assertNotNull(turns, "turns"));
	}

	/**
	 * Remove a {@link Player}s from the list of players participating in the
	 * game.
	 * 
	 * @param player
	 *            the player to remove, must nor be {@code null}.
	 * @return if the player was contained in the list.
	 */
	public boolean removePlayer(final Player player) {
		return players.remove(assertNotNull(player, "player"));
	}

	/**
	 * Remove several {@link Player}s from the list of players participating in
	 * the game.
	 * 
	 * @param players
	 *            the players to remove, must not be {@code null}. May contain
	 *            {@code null}, although this has no effect and is discouraged.
	 * @return if any of the players were contained in the list.
	 */
	public boolean removePlayers(final Collection<Player> players) {
		return this.players.removeAll(assertNotNull(players, "players"));
	}

	/**
	 * Overwrite a single {@link Square} on the board. The {@link Position}
	 * stored in the square is used for positioning.
	 * 
	 * @param square
	 *            the new square, must not be {@code null}.
	 */
	public void setSquare(final Square square) {
		final Position squarePosition = assertNotNull(square, "square")
				.getPosition();
		board[squarePosition.getX()][squarePosition.getY()] = square;
	}

	/**
	 * Overwrite multiple {@link Square}s on the board. The {@link Position}s
	 * stored in the squares are used for positioning.
	 * 
	 * @param squares
	 *            the new squares, must not be {@code null} or contain
	 *            {@code null}.
	 */
	public void setSquares(final Collection<Square> squares) {
		for (final Square square : assertNotNull(squares, "squares")) {
			setSquare(square);
		}
	}

	/**
	 * Set the termination condition for the game.
	 * 
	 * @param terminationCondition
	 *            the new condition for game termination, must not be
	 *            {@code null}.
	 */
	public void setTerminationCondition(
			final TerminationCondition terminationCondition) {
		this.terminationCondition = assertNotNull(terminationCondition,
				"termination condition");
	}

	@Override
	public String toString() {
		return new ToStringBuilder(this, SHORT_PREFIX_STYLE).append(
				"terminationCondition", terminationCondition).toString();
	}

	/**
	 * Remove all {@link Turn}s above a certain turn number from the list of
	 * turns made during the game.
	 * 
	 * @param from
	 *            the lowest position to remove.
	 */
	public void truncateHistory(final int from) {
		history.subList(from, history.size()).clear();
	}

	/**
	 * Remove all {@link Player}s above a turn order position from the list of
	 * players participating in the game.
	 * 
	 * @param from
	 *            the lowest position to remove.
	 */
	public void truncatePlayers(final int from) {
		players.subList(from, players.size()).clear();
	}
}
